import ChainPromiseCall from "@rasir/chain-promise-call";

/**
 * 链式调用工具
 * https://www.npmjs.com/package/@rasir/chain-promise-call
 */
export const chainPromiseCall = new ChainPromiseCall();

const workerCode = `
// 缓存当前页签内容
var tbName = "KEY_VALUE_STORE";
var db;
var dbName;
var expiredTime;
var timerId;
var windowId;
// 初始化indexDb
function openIndexDb() {
  return new Promise((resolve, reject) => {
    var request = indexedDB.open(dbName);
    request.onupgradeneeded = function (e) {
      var db = e.target.result;
      if (!db.objectStoreNames.contains(tbName)) {
        db.createObjectStore(tbName);
      }
      resolve(db);
    };
    request.onsuccess = function (e) {
      var db = e.target.result;
      resolve(db);
    };
    request.onerror = function (e) {
      reject(e);
    };
  });
}

// 获取indexDb中的数据
function promiseGetStorage() {
  return new Promise((resolve, reject) => {
    if (!db) {
      openIndexDb()
        .then((rdb) => {
          db = rdb;
          getStorage(rdb);
          resolve(rdb);
        })
        .catch(reject);
    } else {
      getStorage(db);
      resolve(db);
    }
  });
}

function getStorage(rdb) {
  var tx = rdb.transaction(tbName, "readonly");
  var store = tx.objectStore(tbName);
  var request = store.get(windowId);
  request.onsuccess = function (e) {
    var result = e.target.result;
    sendMessage({
      type: "GET",
      payload: result,
    });
  };
  request.onerror = function (e) {
    sendMessage({
      type: "GET",
      payload: e,
    });
  };
}

// 保存数据
function promiseUpdateStorage(payload) {
  return new Promise((resolve, reject) => {
    if (!db) {
      openIndexDb()
        .then((rdb) => {
          db = rdb;
          updateStorage(rdb, payload);
          resolve(rdb);
        })
        .catch(reject);
    } else {
      updateStorage(db, payload);
      resolve(db);
    }
  });
}
function updateStorage(rdb, { data }) {
  var transaction = rdb.transaction([tbName], "readwrite");
  var store = transaction.objectStore(tbName);
  var data = {
    data,
    expired: Date.now() + expiredTime,
  };
  var request = store.put(data, windowId);
  request.onsuccess = function () {
    sendMessage({
      type: "ADD",
      payload: { ok: true },
    });
  };
  request.onerror = function (e) {
    sendMessage({
      type: "ADD",
      payload: { ok: false },
    });
  };
}

// 删除所有数据
function promiseRemoveStorage() {
  return new Promise((resolve, reject) => {
    if (!db) {
      openIndexDb()
        .then((rdb) => {
          db = rdb;
          removeStorage(rdb, windowId);
          resolve(rdb);
        })
        .catch(reject);
    } else {
      removeStorage(db, windowId);
      resolve(db);
    }
  });
}
function removeStorage(rdb, windowId) {
  var transaction = rdb.transaction([tbName], "readwrite");
  var store = transaction.objectStore(tbName);

  var request = store.delete(windowId);
  request.onsuccess = function (e) {
    sendMessage({
      type: "REMOVE",
      payload: { ok: true },
    });
  };
  request.onerror = function (e) {
    sendMessage({
      type: "REMOVE",
      payload: { ok: false },
    });
  };
}

// 清理过期的数据，给当前窗口的数据续期
function promiseRenewal() {
  return new Promise((resolve, reject) => {
    if (!db) {
      openIndexDb()
        .then((rdb) => {
          db = rdb;
          renewal(rdb);
          resolve(rdb);
        })
        .catch(reject);
    } else {
      renewal(db);
      resolve(db);
    }
  });
}
function renewal(rdb) {
  var transaction = rdb.transaction([tbName], "readwrite");
  var store = transaction.objectStore(tbName);
  store.openCursor().onsuccess = function (event) {
    var cursor = event.target.result;
    if (cursor) {
      var wid = cursor.key;
      var data = cursor.value;
      if (wid === windowId) {
        data.expired += expiredTime;
        store.put(data, windowId);
      } else {
        if (data.expired < Date.now()) {
          removeStorage(rdb, wid);
        }
      }
      // 移动到下一条数据
      cursor.continue();
    }
  };
}

// 定时任务
function autoRenewal() {
  if (timerId) clearInterval(timerId);
  if (expiredTime) {
    timerId = setInterval(promiseRenewal, (expiredTime / 2) >> 0);
  }
  sendMessage({
    type: "START",
    payload: { ok: true },
  });
}

function stopRenewal() {
  if (timerId) clearInterval(timerId);
  sendMessage({
    type: "STOP",
    payload: { ok: true },
  });
}

// 发送信息
function sendMessage(result) {
  self.postMessage(result);
}
self.onmessage = function (event) {
  // 接收到主线程传来的数据
  var { type, payload } = event.data;
  switch (type) {
    case "INIT":
      dbName = payload.dbName;
      expiredTime = payload.expiredTime;
      windowId = payload.windowId;
      openIndexDb().then((ret) => (db = ret));
      break;
    case "GET":
      promiseGetStorage();
      break;
    case "ADD":
      promiseUpdateStorage(payload);
      break;
    case "REMOVE":
      promiseRemoveStorage();
      break;
    case "START":
      autoRenewal();
      break;
    case "STOP":
      stopRenewal();
      break;
    default:
      break;
  }
};`;

function getWorker() {
  const blob = new Blob([workerCode], { type: "application/javascript" });
  const url = window.URL.createObjectURL(blob);
  return new Worker(url);
}

function S4() {
  return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}
function createUUID() {
  return `${S4() + S4()}-${S4()}-${S4()}-${S4()}-${S4()}${S4()}${S4()}`;
}

type WorkerResponseData = {
  type: "GET" | "ADD" | "REMOVE" | "STOP";
  payload: any;
};

type WorkerResponse = {
  data: WorkerResponseData;
};

interface IPageStorage {
  noWindowName?: boolean; // 是否不使用窗口名称作为窗口特征码，默认 false
  windowName?: string; // 当前窗口的特征码，默认 PAGE_STORAGE
  windowId?: string; // 当前窗口的uuid
  expiredTime?: number; // 过期时间，单位秒，默认 10s
  dbName: string; // indexdb中的storeName 必传
  sessionWindowKey: string; // 在 sessionStorage 中保存 windowId 的 key 必传
}

class PageStorage<T = any> {
  private expiredTime: number;

  private windowId: string;

  private dbName: string;

  private sessionWindowKey: string;

  private worker: Worker;

  constructor(props: IPageStorage) {
    if (!props) {
      throw new Error("there is no sessionWindowKey and dbName");
    }
    const {
      expiredTime = 10000,
      dbName,
      sessionWindowKey,
      windowId,
      windowName = "PAGE_STORAGE",
      noWindowName = false,
    } = props;
    if (!sessionWindowKey) {
      throw new Error("there is no sessionWindowKey");
    }
    if (!dbName) {
      throw new Error("there is no dbName");
    }
    this.expiredTime = expiredTime;
    this.dbName = dbName;
    this.sessionWindowKey = sessionWindowKey;
    if (!noWindowName && !(window.name || "").includes(windowName)) {
      sessionStorage.removeItem(this.sessionWindowKey);
      window.name = `${window.name}_${windowName}_${createUUID()}`;
    }
    this.windowId =
      windowId || sessionStorage.getItem(this.sessionWindowKey) || createUUID();
    this.worker = getWorker();
    this.worker.postMessage({
      type: "INIT",
      payload: {
        dbName: this.dbName,
        expiredTime: this.expiredTime,
        windowId: this.windowId,
      },
    });
    sessionStorage.setItem(this.sessionWindowKey, this.windowId);
    this.worker.postMessage({ type: "START" });
  }

  /**
   * 链式获取当前窗口缓存的数据
   * @returns
   */
  public chainGetPageStorage(): Promise<T | undefined> {
    return new Promise((resolve, reject) => {
      chainPromiseCall.push({
        run: async () => await this.getPageStorage(),
        onSuccess: (result: T) => resolve(result),
        onError: (err) => reject(err),
      });
    });
  }

  /**
   * 获取当前窗口缓存的数据
   * @returns
   */
  public getPageStorage(): Promise<T | undefined> {
    return new Promise((resolve) => {
      this.worker.postMessage({
        type: "GET",
        payload: { windowId: this.windowId },
      });
      this.worker.onmessage = ({ data: { type, payload } }: WorkerResponse) => {
        if (type === "GET") {
          resolve((payload || {}).data);
        }
      };
    });
  }

  /**
   * 链式保存当前窗口缓存的数据
   * @param data
   * @returns
   */
  public chainSavePageStorage(data: T): Promise<Boolean> {
    return new Promise((resolve, reject) => {
      chainPromiseCall.push({
        run: async () => await this.savePageStorage(data),
        onSuccess: (result: Boolean) => resolve(result),
        onError: (err) => reject(err),
      });
    });
  }

  /**
   * 保存当前窗口缓存的数据
   * @param data
   * @returns
   */
  public savePageStorage(data: T): Promise<boolean> {
    return new Promise((resolve) => {
      this.worker.postMessage({
        type: "ADD",
        payload: { data },
      });

      this.worker.onmessage = ({ data: { type, payload } }: WorkerResponse) => {
        if (type === "ADD") {
          resolve(payload.ok);
        }
      };
    });
  }

  /**
   * 链式修改当前窗口缓存的数据
   * @param callback
   * @returns
   */
  public chainUpdatePageStorage(
    callback: (data?: T) => T | Promise<T>
  ): Promise<Boolean> {
    return new Promise((resolve, reject) => {
      chainPromiseCall.push({
        run: async () => await this.updatePageStorage(callback),
        onSuccess: (result: Boolean) => resolve(result),
        onError: (err) => reject(err),
      });
    });
  }

  /**
   * 修改当前窗口缓存的数据
   * @param callback
   * @returns
   */
  public updatePageStorage(
    callback: (data?: T) => T | Promise<T>
  ): Promise<boolean> {
    return new Promise(async (resolve) => {
      const contents = await this.getPageStorage();
      const data = await callback(contents);
      this.worker.postMessage({
        type: "ADD",
        payload: { data },
      });

      this.worker.onmessage = ({ data: { type, payload } }: WorkerResponse) => {
        if (type === "ADD") {
          resolve(payload.ok);
        }
      };
    });
  }

  /**
   * 链式删除当前窗口缓存的数据
   * @returns
   */
  public chainRemovePageStorage(): Promise<Boolean> {
    return new Promise((resolve, reject) => {
      chainPromiseCall.push({
        run: async () => await this.removePageStorage(),
        onSuccess: (result: Boolean) => resolve(result),
        onError: (err) => reject(err),
      });
    });
  }

  /**
   * 删除当前窗口缓存的数据
   * @returns
   */
  public removePageStorage(): Promise<boolean> {
    return new Promise((resolve) => {
      this.windowId = createUUID();
      sessionStorage.setItem(this.sessionWindowKey, this.windowId);
      this.worker.postMessage({
        type: "REMOVE",
      });
      this.worker.onmessage = ({ data: { type, payload } }: WorkerResponse) => {
        if (type === "REMOVE") {
          resolve(payload.ok);
        }
      };
    });
  }

  /**
   * 链式取消对当前窗口数据的缓存
   * @returns
   */
  public chainDispose(): Promise<Boolean> {
    return new Promise((resolve, reject) => {
      chainPromiseCall.push({
        run: async () => await this.dispose(),
        onSuccess: (result: Boolean) => resolve(result),
        onError: (err) => reject(err),
      });
    });
  }

  /**
   * 取消对当前窗口数据的缓存
   * @returns
   */
  public dispose(): Promise<boolean> {
    return new Promise((resolve) => {
      this.worker.postMessage({
        type: "STOP",
      });
      this.worker.onmessage = ({ data: { type, payload } }: WorkerResponse) => {
        if (type === "STOP") {
          resolve(payload.ok);
        }
      };
    });
  }
}

export default PageStorage;
